/**
 * TCP Server
 * Long connection handler
 */

#include "tcpsrv.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <err.h>
#include <fcntl.h>
#include <time.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

// Libevent needed!!!
// http://www.monkey.org/~provos/libevent/
#include <event.h>


//queue start
#include <pthread.h>
#include "evutil.h"

extern struct queue_table * queue_head[3];
extern struct event_table * event_head;
//queue end

// A client list
// In 32-bit system, it can handle 65535 connections totally
struct conn_client *client_list[MAX_CLIENT];

/* Process daemonize */
int conn_daemonize()
{
	int fd;
	
	switch(fork())
	{
		case -1 :
			return -1;
		case 0 :
			break;
		default :
			exit(1);
	}
	
	if (setsid() == -1)
	{
		return -1;
	}
	
	fd = open("/dev/null", O_RDWR, 0);
	if (fd)
	{
		(void) dup2(fd, STDIN_FILENO);
		(void) dup2(fd, STDOUT_FILENO);
		(void) dup2(fd, STDERR_FILENO);
		if (fd > STDERR_FILENO)
		{
			(void) close(fd);
		}
	}
	
	return 0;
}

/* Set non-blocking */
int conn_setnonblock(int fd)
{
	int flags;
	
	flags = fcntl(fd, F_GETFL);
	if (flags < 0)
	{
		return flags;
	}
	
	flags |= O_NONBLOCK;
	if (fcntl(fd, F_SETFL, flags) < 0)
	{
		return -1;
	}
	
	return 0;
}

/* Make a new TCP connection */
struct conn_client * conn_from_list()
{
	// Find a empty client slot
	int i;
	struct conn_client *tmp;
	
	for (i = 0; i < MAX_CLIENT; i ++)
	{
		if (!client_list[i])
		{
			// An empty item
			tmp = (struct conn_client *) malloc(sizeof(struct conn_client));
			client_list[i] = tmp;
			tmp->id = i;
			//fprintf(stderr, "%d\n", i);
			return tmp;
		}
		
		if (0 == client_list[i]->conn_time)
		{
			// An unused item
			tmp = client_list[i];
			tmp->id = i;
			//fprintf(stderr, "%d\n", i);
			return tmp;
		}
	}
	
	return (struct conn_client *) NULL;
}

/* Free a connection */
void conn_free(struct conn_client *client)
{
	bufferevent_free(client->buffer_event);
	shutdown(client->client_fd, 2);
	close(client->client_fd);

	// Return the fd to freelist
	client->conn_time = 0;
	client->client_fd = 0;
}

/* Send data to a socket fd */
int conn_send(struct conn_client *client, unsigned char *msg)
{
	int len;
	
	if (!client->client_fd)
	{
		return 0;
	}

//	fprintf(stderr,"len:%d\n",strlen(msg));	
	len = send(client->client_fd, msg, strlen(msg), 0);
	if (-1 == len)
	{
		fprintf(stderr, "Client: %d fd: %d -> ", client->id, client->client_fd);
	}
	send(client->client_fd, "\n", 1, 0);
	
	return len + 1;
}

/* Buffer on read */
void conn_on_read(struct bufferevent *bev, void *arg)
{
	struct conn_client *client = (struct conn_client *) arg;
	
	// Depart
	unsigned char buff[BUFFER_SIZE], tmp_name[32];
	int i, j, to_id;
	
	i = bufferevent_read(bev, buff, BUFFER_SIZE - 1);
	
	// String hook?
	process_cmd(client, buff);
}

/* Buffer on write */
void conn_on_write(struct bufferevent *bev, void *arg)
{
	// DO NOTHING
}

/* Buffer on error */
void conn_on_error(struct bufferevent *bev, short what, void *arg)
{
	struct conn_client *client = (struct conn_client *) arg;
	
	if (what & EVBUFFER_EOF)
	{
		// Client end
	}
	
	else
	{
		perror("Client socket error, disconnected.");
	}
	
	conn_free(client);
}

/* Connection accept */
void conn_accept(int fd, short ev, void *arg)
{
	int client_fd;
	struct sockaddr_in client_addr;
	socklen_t client_len = sizeof(client_addr);
	struct conn_client *client;
	
	client = conn_from_list();
	if (!client)
	{
		perror("Server Limit");
		return;
	}
	
	client_fd = accept(fd, (struct sockaddr *) &client_addr, &client_len);
	if (-1 == client_fd)
	{
		perror("Accept");
	}
	
	// Client initialize
	client->client_fd = client_fd;
	client->ipv4_addr = inet_ntoa(client_addr.sin_addr);
	client->ipv6_addr = NULL;
	client->buffer_event = bufferevent_new(client_fd, conn_on_read, conn_on_write, conn_on_error, client);
	client->conn_time = time((time_t *) NULL);


	// Buffer event
	bufferevent_enable(client->buffer_event, EV_READ);
	
	// Test
	
	//int i = conn_send(client, "VERSION 0.0.3");
	//fprintf(stderr, "%d bytes sent\n", i);
	//process_cmd(client, buff);
}

// Timer
void timer_cb (int fd, short ev, void * arg)
{
	static unsigned long ct = 0;
	struct event * timeout = arg;
	struct timeval tv;
	pthread_t h_tid, m_tid, s_tid;
	
	// Hourly queue
	if (0 == ct % 3600)
	{
		pthread_create(&h_tid, NULL, loop_queue_table, "0");
	}
	
	// Minutely queue
	if (0 == ct % 60)
	{
		pthread_create(&m_tid, NULL, loop_queue_table, "1");
	}
	// Secondly queue
	pthread_create(&s_tid, NULL, loop_queue_table, "2");
	
	evutil_timerclear(&tv);
	tv . tv_sec = 1;
	tv . tv_usec = 0;
	event_add(timeout, &tv);
}

/* Connection initialize */
int conn_init(int daemonize)
{

	int listen_fd;
	int reuseaddr_on;
	int buffer_size;
	struct timeval timeout,tv;
	struct sockaddr_in listen_addr;
	struct event ev_accept, ev_timer;
	struct event_base *ev_base;
	struct sigaction sa;

	ev_base = event_init();

	//mysqlconnect();

	listen_fd = socket(AF_INET, SOCK_STREAM, 0);
	if (listen_fd < 0)
	{
		err(1, "Socket failed");
	}
	
	memset(&listen_addr, 0, sizeof(listen_addr));
	listen_addr.sin_family = AF_INET;
	listen_addr.sin_addr.s_addr = INADDR_ANY;
	listen_addr.sin_port = htons(SERVER_PORT);
	
	if (bind(listen_fd, (struct sockaddr *) &listen_addr, sizeof(listen_addr)) < 0)
	{
		err(1, "Bind failed");
	}
	
	if (listen(listen_fd, 5) < 0)
	{
		err(1, "Listen failed");
	}
	
	// Set reuse and send timeout
	reuseaddr_on = 1;
	timeout.tv_sec = 3;
	buffer_size = 16384;
	setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr_on, sizeof(reuseaddr_on));
	setsockopt(listen_fd, SOL_SOCKET, SO_SNDTIMEO, (char *) &timeout, sizeof(struct timeval));
	setsockopt(listen_fd, SOL_SOCKET, SO_SNDBUF, &buffer_size, sizeof(buffer_size));
	setsockopt(listen_fd, SOL_SOCKET, SO_RCVBUF, &buffer_size, sizeof(buffer_size));
	
	if (conn_setnonblock(listen_fd) < 0)
	{
		err(1, "Set non-blocking failed");
	}
	
	queue_table_init();
	event_table_init();

	if (daemonize)
	{
		conn_daemonize();
	}
	
	// Event add
	event_set(&ev_accept, listen_fd, EV_READ|EV_PERSIST, conn_accept, NULL);
	event_add(&ev_accept, NULL);
	event_base_set(ev_base, &ev_accept);
	
	evtimer_set(&ev_timer, timer_cb, &ev_timer);
	evutil_timerclear(&tv);
	tv . tv_sec = 0;
	tv . tv_usec = 0;
	event_add(&ev_timer, &tv);

	// Ignore SIGPIPE & SIGCLD
	sa.sa_handler = SIG_IGN;
	sa.sa_flags = 0;
	signal(SIGPIPE, SIG_IGN);
	if (sigemptyset(&sa.sa_mask) == -1 || sigaction(SIGPIPE, &sa, 0) == -1)
	{
		err(1, "Ignore SIGPIPE failed");
	}
	
	// Event loop
	event_base_loop(ev_base, 0);
	
	return 0;
}
